// This is the main DLL file.

#include "stdafx.h"

#include "BasicLibrary.h"
#include "ImageRendering.h"
#include <math.h>

#pragma unmanaged

void DoFilterImageRGB(int pixelSize, int *pkernel, unsigned char *src, unsigned char *dst, 
				   int startX, int startY, int stopX, int stopY, int size, int srcStride, int divisor, bool dynamicDivisorForEdges,
				   int threshold, int srcOffset, int dstOffset)
{
	// loop and array indexes
    int i, j, t, k, ir, jr;
    // kernel's radius
    int radius = size >> 1;
    // color sums
    long r, g, b, div;

    // kernel size
    int kernelSize = size * size;

	// number of kernel elements taken into account
	int processedKernelSize;
	unsigned char* p;

	// for each line
    for (int y = startY; y < stopY; y++)
    {
        // for each pixel
        for (int x = startX; x < stopX; x++, src += pixelSize, dst += pixelSize)
        {
            r = g = b = div = processedKernelSize = 0;

			int kernelOffset;

            // for each kernel row
            for (i = 0, kernelOffset = 0; i < size; i++, kernelOffset += size)
            {
                ir = i - radius;
                t = y + ir;

                // skip row / break
                if (t < startY || t >= stopY)
                    break;

                // for each kernel column
                for (j = 0; j < size; j++)
                {
                    jr = j - radius;
                    t = x + jr;

                    // skip column
                    if (t < startX)
                        continue;

                    if (t < stopX)
                    {                                            
                        k = pkernel[kernelOffset + j];

                        p = &src[ir * srcStride + jr * pixelSize];

                        div += k;

                        r += k * p[2];
                        g += k * p[1];
                        b += k * p[0];

                        processedKernelSize++;
                    }
                }
            }

            // check if all kernel elements were processed
            if (processedKernelSize == kernelSize)
            {
                // all kernel elements are processed - we are not on the edge
                div = divisor;
            }
            else
            {
                // we are on edge. do we need to use dynamic divisor or not?
                if (!dynamicDivisorForEdges)
                {
                    // do
                    div = divisor;
                }
            }

            // check divider
            if (div != 0)
            {
                r /= div;
                g /= div;
                b /= div;
            }

            r += threshold;
            g += threshold;
            b += threshold;

            dst[2] = (unsigned char)((r > 255) ? 255 : ((r < 0) ? 0 : r));
            dst[1] = (unsigned char)((g > 255) ? 255 : ((g < 0) ? 0 : g));
            dst[0] = (unsigned char)((b > 255) ? 255 : ((b < 0) ? 0 : b));
        }
        src += srcOffset;
        dst += dstOffset;
    }
}

void DoFilterImageGrayscale(int pixelSize, int *pkernel, unsigned char *src, unsigned char *dst, 
				   int startX, int startY, int stopX, int stopY, int size, int srcStride, int divisor, bool dynamicDivisorForEdges,
				   int threshold, int srcOffset, int dstOffset)
{
	// loop and array indexes
    int i, j, t, k, ir, jr;
    // kernel's radius
    int radius = size >> 1;
    // color sums
    long g, div;

    // kernel size
    int kernelSize = size * size;

	// number of kernel elements taken into account
	int processedKernelSize;	

	// for each line
	for (int y = startY; y < stopY; y++)
    {
        // for each pixel
        for (int x = startX; x < stopX; x++, src++, dst++)
        {
            g = div = processedKernelSize = 0;

            // for each kernel row
            for (i = 0; i < size; i++)
            {
                int kernelOffset = i * size;
                //kernelOffset *= 4;

                ir = i - radius;
                t = y + ir;

                // skip row
                if (t < startY)
                    continue;
                // break
                if (t >= stopY)
                    break;

                // for each kernel column
                for (j = 0; j < size; j++)
                {
                    jr = j - radius;
                    t = x + jr;

                    // skip column
                    if (t < startX)
                        continue;

                    if (t < stopX)
                    {
                        // k = kernel[i, j];
                        // k = *(pkernel + kernelOffset + (j * 4));
                        k = pkernel[kernelOffset + j];

                        div += k;
                        g += k * src[ir * srcStride + jr];
                        processedKernelSize++;
                    }
                }
            }

            // check if all kernel elements were processed
            if (processedKernelSize == kernelSize)
            {
                // all kernel elements are processed - we are not on the edge
                div = divisor;
            }
            else
            {
                // we are on edge. do we need to use dynamic divisor or not?
                if (!dynamicDivisorForEdges)
                {
                    // do
                    div = divisor;
                }
            }

            // check divider
            if (div != 0)
            {
                g /= div;
            }
            g += threshold;
            *dst = (unsigned char)((g > 255) ? 255 : ((g < 0) ? 0 : g));
        }
        src += srcOffset;
        dst += dstOffset;
    }
}

void ResizeBicubic(unsigned char *src, unsigned char *dst, double xFactor, double yFactor, int height, int width, int newHeight, int newWidth, int srcStride, int dstOffset)
{
	double  ox, oy, dx, dy, k1, k2;
    int     ox1, oy1, ox2, oy2;
    // destination pixel values
    double r, g, b;
    // width and height decreased by 1
    int ymax = height - 1;
    int xmax = width - 1;
	unsigned char *p;

	// RGB
    for ( int y = 0; y < newHeight; y++ )
    {
        // Y coordinates
        oy  = (double) y * yFactor - 0.5f;
        oy1 = (int) oy;
        dy  = oy - (double) oy1;

        for ( int x = 0; x < newWidth; x++, dst += 3 )
        {
            // X coordinates
            ox  = (double) x * xFactor - 0.5f;
            ox1 = (int) ox;
            dx  = ox - (double) ox1;

            // initial pixel value
            r = g = b = 0;

            for ( int n = -1; n < 3; n++ )
            {
                // get Y cooefficient
				double bi = dy - (double)n;

				if ( bi > 2.0 )
				{
					k1 = 0.0;
				}
				else
				{
					double a, b, c, d;
					double xm1 = bi - 1.0;
					double xp1 = bi + 1.0;
					double xp2 = bi + 2.0;

					a = ( xp2 <= 0.0 ) ? 0.0 : xp2 * xp2 * xp2;
					b = ( xp1 <= 0.0 ) ? 0.0 : xp1 * xp1 * xp1;
					c = ( bi <= 0.0 )   ? 0.0 : bi * bi * bi;
					d = ( xm1 <= 0.0 ) ? 0.0 : xm1 * xm1 * xm1;

					k1 = ( 0.16666666666666666667 * ( a - ( 4.0 * b ) + ( 6.0 * c ) - ( 4.0 * d ) ) );
				}
                // k1 = BiCubicKernel( dy - (double) n );

                oy2 = oy1 + n;
                if ( oy2 < 0 )
                    oy2 = 0;
                if ( oy2 > ymax )
                    oy2 = ymax;

                for ( int m = -1; m < 3; m++ )
                {
                    // get X cooefficient
					double bi = (double)m - dx;

					if ( bi > 2.0 )
					{
						k2 = 0.0;
					}
					else
					{
						double a, b, c, d;
						double xm1 = bi - 1.0;
						double xp1 = bi + 1.0;
						double xp2 = bi + 2.0;

						a = ( xp2 <= 0.0 ) ? 0.0 : xp2 * xp2 * xp2;
						b = ( xp1 <= 0.0 ) ? 0.0 : xp1 * xp1 * xp1;
						c = ( bi <= 0.0 )   ? 0.0 : bi * bi * bi;
						d = ( xm1 <= 0.0 ) ? 0.0 : xm1 * xm1 * xm1;

						k2 = k1 * ( 0.16666666666666666667 * ( a - ( 4.0 * b ) + ( 6.0 * c ) - ( 4.0 * d ) ) );
					}

                    // k2 = k1 * BiCubicKernel( (double) m - dx );

                    ox2 = ox1 + m;
                    if ( ox2 < 0 )
                        ox2 = 0;
                    if ( ox2 > xmax )
                        ox2 = xmax;

                    // get pixel of original image
                    p = src + oy2 * srcStride + ox2 * 3;

                    r += k2 * p[2];
                    g += k2 * p[1];
                    b += k2 * p[0];
                }
            }

            dst[2] = (unsigned char) r;
            dst[1] = (unsigned char) g;
            dst[0] = (unsigned char) b;
        }
        dst += dstOffset;
    }
}

void ResizeBilinear(unsigned char *src, unsigned char *dst, double xFactor, double yFactor, int height, int width, int newHeight, int newWidth, int srcStride, int dstOffset, int pixelSize)
{
	// coordinates of source points
    double  ox, oy, dx1, dy1, dx2, dy2;
    int     ox1, oy1, ox2, oy2;
    // width and height decreased by 1
    int ymax = height - 1;
    int xmax = width - 1;
    // temporary pointers    
    unsigned char* p1;
	unsigned char* p2;
	unsigned char* p3;
	unsigned char* p4;
	
	// for each line
    for ( int y = 0; y < newHeight; y++ )
    {
        // Y coordinates
        oy  = (double) y * yFactor;
        oy1 = (int) oy;
        oy2 = ( oy1 == ymax ) ? oy1 : oy1 + 1;
        dy1 = oy - (double) oy1;
        dy2 = 1.0 - dy1;

        // get temp pointers
        unsigned char *tp1 = src + oy1 * srcStride;
        unsigned char *tp2 = src + oy2 * srcStride;

        // for each pixel
        for ( int x = 0; x < newWidth; x++ )
        {
            // X coordinates
            ox  = (double) x * xFactor;
            ox1 = (int) ox;
            ox2 = ( ox1 == xmax ) ? ox1 : ox1 + 1;
            dx1 = ox - (double) ox1;
            dx2 = 1.0 - dx1;

            // get four points
            p1 = tp1 + ox1 * pixelSize;
            p2 = tp1 + ox2 * pixelSize;
            p3 = tp2 + ox1 * pixelSize;
            p4 = tp2 + ox2 * pixelSize;

            // interpolate using 4 points
            for ( int i = 0; i < pixelSize; i++, dst++, p1++, p2++, p3++, p4++ )
            {
                *dst = (unsigned char) (
                    dy2 * ( dx2 * ( *p1 ) + dx1 * ( *p2 ) ) +
                    dy1 * ( dx2 * ( *p3 ) + dx1 * ( *p4 ) ) );
            }
        }
        dst += dstOffset;
    }
}

void ResizeNearestNeighbor3(unsigned char *baseSrc, unsigned char *baseDst, double xFactor, double yFactor, int height, int width, int newHeight, int newWidth, int pixelSize, int srcStride, int dstStride)
{
	for ( int y = 0; y < newHeight; y++ )
    {
		int srcOffset = srcStride * ( (int) ( y * yFactor ) );
        unsigned char* dst = baseDst + dstStride * y;
		unsigned char* src = baseSrc + srcOffset;
        unsigned char* p;

        // for each pixel
        for ( int x = 0; x < newWidth; x++ )
        {
            p = src + pixelSize * ( (int) ( x * xFactor ) );
			
			*(dst+2) = *(p+2);
			*(dst+1) = *(p+1);
			*dst = *p;			

			dst += 3;
			p+=3;            
        }
    }
}

void FilterHSLLinear(int pixelSize, unsigned char *ptr, int startX, int startY, int stopX, int stopY, int offset,
				   double inLuminanceMin, double inLuminanceMax, double outLuminanceMin, double outLuminanceMax,
				   double inSaturationMin, double inSaturationMax, double outSaturationMin, double outSaturationMax)
{
	double kl = 0;
	double bl = 0;
    double ks = 0;
	double bs = 0;

    // luminance line parameters
    if (inLuminanceMax != inLuminanceMin)
    {
        kl = (outLuminanceMax - outLuminanceMin) / (inLuminanceMax - inLuminanceMin);
        bl = outLuminanceMin - kl * inLuminanceMin;
    }
    // saturation line parameters
    if (inSaturationMax != inSaturationMin)
    {
        ks = (outSaturationMax - outSaturationMin) / (inSaturationMax - inSaturationMin);
        bs = outSaturationMin - ks * inSaturationMin;
    }
    
    // for each row
    for (int y = startY; y < stopY; y++)
    {
        // for each pixel
        for (int x = startX; x < stopX; x++, ptr += pixelSize)
        {
            unsigned char red = ptr[2];
            unsigned char green = ptr[1];
            unsigned char blue = ptr[0];

			int hue;
			double saturation;
			double luminance;

			RGBToHSL(red, green, blue, &hue, &saturation, &luminance);
            
            // do luminance correction
            if (luminance >= inLuminanceMax)
                luminance = outLuminanceMax;
            else if (luminance <= inLuminanceMin)
                luminance = outLuminanceMin;
            else
                luminance = kl * luminance + bl;

            // do saturation correct correction
            if (saturation >= inSaturationMax)
                saturation = outSaturationMax;
            else if (saturation <= inSaturationMin)
                saturation = outSaturationMin;
            else
                saturation = ks * saturation + bs;

            // convert back to RGB

			CalcRGBFromHSL(hue, luminance, saturation, ptr+2, ptr+1, ptr);
            //AForge.Imaging.HSL.ToRGB(hsl, rgb);

            /*ptr[2] = rgb.Red;
            ptr[1] = rgb.Green;
            ptr[0] = rgb.Blue;*/
        }
        ptr += offset;
    }
}

void SobelEdge( unsigned char *src, unsigned char *dst, unsigned char *dst2, int dstOffset, int srcOffset, int dstStride, int srcStride, int startX, int startY, int stopX, int stopY, bool scaleIntensity)
{
    // variables for gradient calculation
    double g = 0;
	double max = 0;

    // for each line
    for ( int y = startY; y < stopY; y++ )
    {
        // for each pixel
        for ( int x = startX; x < stopX; x++, src++, dst++ )
        {
			int sum1 = src[-srcStride - 1] + src[-srcStride + 1]
                        - src[ srcStride - 1] - src[ srcStride + 1]
                        + 2 * ( src[-srcStride] - src[srcStride] ) ;
			int sum2 = src[-srcStride + 1] + src[srcStride + 1]
                        - src[-srcStride - 1] - src[srcStride - 1]
                        + 2 * ( src[1] - src[-1] );

			int sumAbs = abs(sum1) + abs(sum2);

			g = 255 > sumAbs ? sumAbs : 255;

            if ( g > max )
                max = g;

            *dst = (unsigned char) g;
        }
        src += srcOffset;
        dst += dstOffset;
    }

    // do we need scaling
    if ( ( scaleIntensity ) && ( max != 255 ) )
    {
        // make the second pass for intensity scaling
        double factor = 255.0 / (double) max;
        dst = (unsigned char*)dst2;// destination.ImageData.ToPointer( ) + dstStride;

        // for each line
        for ( int y = startY; y < stopY; y++ )
        {
            dst++;
            // for each pixel
            for ( int x = startX; x < stopX; x++, dst++ )
            {
                *dst = (unsigned char) ( factor * *dst );
            }
            dst += dstOffset;
        }
    }
}